/*  cpuModel.S
 *
 *  This file contains all assembly code for the Intel Cpu identification.
 *  It is based on linux cpu detection code.
 *
 *  Intel also provides public similar code in the book
 *  called :
 *	
 *	Pentium Processor Family
 *		Developer Family
 *	Volume  3 :	Architecture and Programming Manual
 *
 * At the following place :
 *	
 *	Chapter 5 :	Feature determination
 *	Chapter 25:	CPUID instruction	
 *
 *  COPYRIGHT (c) 1998 valette@crf.canon.fr
 *
 *  The license and distribution terms for this file may be
 *  found in the file LICENSE in this distribution or at
 *  http://www.OARcorp.com/rtems/license.html.
 *
 *  $Id$
 */

#include <asm.h>
#include <libcpu/registers.h>

BEGIN_CODE
	PUBLIC(checkCPUtypeSetCr0); 
/* 
 * check Processor type: 386, 486, 6x86(L) or CPUID capable processor 
 */

SYM (checkCPUtypeSetCr0):
	/*
	 *  Assume 386 for now 
	 */
	movl $3, SYM (x86) 
	/*
	 * Start using the EFAGS AC bit determination method described in
	 * the book mentionned above page 5.1. If this bit can be set we
	 * have a 486 or above.
	 */
	pushfl				/* save EFLAGS			*/
	
	pushfl				/* Get EFLAGS in EAX		*/
	popl eax
	
	movl eax,ecx			/* save original EFLAGS in ECX	*/
	xorl $EFLAGS_ALIGN_CHECK,eax	/* flip AC bit in EAX		*/
	pushl eax			/* set EAX as EFLAGS		*/
	popfl			
	pushfl				/* Get new EFLAGS in EAX	*/
	popl eax
	
	xorl ecx,eax			/* check if AC bit changed	*/
	andl $EFLAGS_ALIGN_CHECK,eax	
	je is386			/* If not : we have a 386	*/
	/*
	 *  Assume 486 for now 
	 */
	movl $4,SYM (x86)
	movl ecx,eax			/* Restore orig EFLAGS in EAX	*/
	xorl $EFLAGS_ID,eax		/* flip  ID flag		*/
	pushl eax			/* set EAX as EFLAGS		*/
	popfl				
	pushfl				/* Get new EFLAGS in EAX	*/
	popl eax	
			
	xorl ecx,eax			/* check if ID bit changed	*/
	andl $EFLAGS_ID,eax

	/* 
	 * if we are on a straight 486DX,
	 * SX, or 487SX we can't change it
	 * OTOH 6x86MXs and MIIs check OK 
	 * Also if we are on a Cyrix 6x86(L)
	 */
	je is486x

isnew:	
	/*
	 * restore original EFLAGS
	 */
	popfl
	incl SYM(have_cpuid)	/* we have CPUID instruction */

	/* use it to get :	
	 *	processor type,
	 *	processor model,
	 *	processor mask,
	 * by using it with EAX = 1
	 */
	movl $1, eax		
	cpuid			

	movb al, cl		/* save reg for future use */
	
	andb $0x0f,ah		/* mask processor family   */
	movb ah,SYM (x86)	/* put result in x86 var   */
	
	andb $0xf0, al		/* get model		   */
	shrb $4, al
	movb al,SYM (x86_model) /* store it in x86_model   */
	
	andb $0x0f, cl		/* get mask revision	   */
	movb cl,SYM (x86_mask)  /* store it in x86_mask	   */
	
	movl edx,SYM(x86_capability)	/* store feature flags in x86_capability */
	
	/* get vendor info by using CPUID with EXA = 0 */
	xorl eax, eax		
	cpuid

	/*
	 * store results contained in ebx, edx, ecx in
	 * x86_vendor_id variable.
	 */
	movl ebx,SYM(x86_vendor_id)	
	movl edx,SYM(x86_vendor_id)+4	
	movl ecx,SYM(x86_vendor_id)+8	

	movl cr0,eax		/* 486+ */
	andl $(CR0_PAGING | CR0_PROTECTION_ENABLE | CR0_EXTENSION_TYPE), eax
	orl $(CR0_ALIGMENT_MASK | CR0_WRITE_PROTECT | CR0_NUMERIC_ERROR | CR0_MONITOR_COPROC),eax
	jmp 2f

/* Now we test if we have a Cyrix 6x86(L). We didn't test before to avoid
 * clobbering the new BX chipset used with the Pentium II, which has a register
 * at the same addresses as those used to access the Cyrix special configuration
 * registers (CCRs).
 */
	/*
	 * A Cyrix/IBM 6x86(L) preserves flags after dividing 5 by 2
	 * (and it _must_ be 5 divided by 2) while other CPUs change
	 * them in undefined ways. We need to know this since we may
	 * need to enable the CPUID instruction at least.
	 * We couldn't use this test before since the PPro and PII behave
	 * like Cyrix chips in this respect.
	 */
is486x:	xor ax,ax
	sahf
	movb $5,ax
	movb $2,bx
	div bl
	lahf
	cmpb $2,ah
	jne ncyrix
	/*
	 * N.B. The pattern of accesses to 0x22 and 0x23 is *essential*
	 *      so do not try to "optimize" it! For the same reason we
	 *	do all this with interrupts off.
	 */
#define setCx86(reg, val) \
	movb reg,ax;	\
	outb ax,$0x22;	\
	movb val,ax;	\
	outb ax,$0x23

#define getCx86(reg) \
	movb reg,ax;	\
	outb ax,$0x22;	\
	inb $0x23,ax

	cli
	getCx86($0xc3)		/*  get CCR3 */
	movb ax,cx		/* Save old value */
	movb ax,bx
	andb $0x0f,bx		/* Enable access to all config registers */
	orb $0x10,bx		/* by setting bit 4 */
	setCx86($0xc3,bx)

	getCx86($0xe8)		/* now we can get CCR4 */
	orb $0x80,ax		/* and set bit 7 (CPUIDEN) */
	movb ax,bx		/* to enable CPUID execution */
	setCx86($0xe8,bx)

        getCx86($0xfe)          /* DIR0 : let's check this is a 6x86(L) */
        andb $0xf0,ax		/* should be 3xh */
	cmpb $0x30,ax		
	jne n6x86
        getCx86($0xe9)          /* CCR5 : we reset the SLOP bit */
        andb $0xfd,ax		/* so that udelay calculation */
        movb ax,bx		/* is correct on 6x86(L) CPUs */
        setCx86($0xe9,bx)
	setCx86($0xc3,cx)	/* Restore old CCR3 */
	sti
	jmp isnew		/* We enabled CPUID now */

n6x86:	setCx86($0xc3,cx)	/* Restore old CCR3 */
	sti
ncyrix:				/* restore original EFLAGS */
	popfl
	movl cr0,eax		/* 486 */
	andl $(CR0_PAGING | CR0_EXTENSION_TYPE | CR0_PROTECTION_ENABLE),eax	/* Save PG,PE,ET */
	orl $(CR0_ALIGMENT_MASK | CR0_WRITE_PROTECT | CR0_NUMERIC_ERROR | CR0_MONITOR_COPROC),eax	/* set AM, WP, NE and MP */
	jmp 2f
is386:				/* restore original EFLAGS */
	popfl
	movl cr0,eax		/* 386 */
	andl $(CR0_PAGING | CR0_EXTENSION_TYPE | CR0_PROTECTION_ENABLE),eax	/* Save PG,PE,ET */
	orl $CR0_MONITOR_COPROC,eax		/* set MP */
2:	movl eax,cr0
	call check_x87
	ret
	

/*
 * We depend on ET to be correct. This checks for 287/387.
 */
check_x87:
	movb $0,SYM(hard_math)
	clts
	fninit
	fstsw ax
	cmpb $0,al
	je 1f
	movl cr0,eax		/* no coprocessor: have to set bits */
	xorl $4,eax		/* set EM */
	movl eax,cr0
	ret
	.align 16
1:	movb $1,SYM(hard_math)
	.byte 0xDB,0xE4		/* fsetpm for 287, ignored by 387 */
	ret

END_CODE
	
BEGIN_DATA
	PUBLIC(x86)
	PUBLIC(have_cpuid)
	PUBLIC(x86_model)
	PUBLIC(x86_mask)
	PUBLIC(x86_capability)
	PUBLIC(x86_vendor_id)
	PUBLIC(hard_math)

SYM(x86):	
	.byte 0
SYM(have_cpuid):	
	.long 0
SYM(x86_model):	
	.byte 0
SYM(x86_mask):	
	.byte 0
SYM(x86_capability):	
	.long 0		
SYM(x86_vendor_id):	
	.zero 13
SYM(hard_math):	
	.byte 0
END_DATA
	
